//****************************************************************************
// Copyright (C) 2001,2002,2003,2004  PEAK System-Technik GmbH
//
// linux@peak-system.com
// www.peak-system.com
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
// Maintainer(s): Klaus Hitschler (klaus.hitschler@gmx.de)
//
// Contributions: Marcel Offermans (marcel.offermans@luminis.nl)
//                Philipp Baer (philipp.baer@informatik.uni-ulm.de)
//                Garth Zeglin (garthz@ri.cmu.edu)
//****************************************************************************

//****************************************************************************
//
// pcan_main.c - the starting point of the driver,
//               init and cleanup and proc interface
//
// $Log: pcan_main.c,v $
// Revision 1.70  2004/11/21 20:33:04  klaus
// test release to get serial number bug
//
// Revision 1.69  2004/05/02 15:56:52  klaus
// Last corrections and improvements, especially for Makefile(s) and build scenarios
//
// Revision 1.68  2004/05/02 12:06:02  klaus
// total rebuild of Makefile for KBUILD support for Kernel 2.6
//
// Revision 1.67  2004/04/18 17:59:13  klaus
// added MODULE_VERSION support for kernel 2.6.x
//
// Revision 1.66  2004/04/13 19:43:35  klaus
// release_20040413_x; improved timestamp calculation for USB
//
// Revision 1.65  2004/04/12 16:53:27  klaus
// improved timer resolution for kernel 2.6
//
// Revision 1.64  2004/04/12 16:53:26  klaus
// improved timer resolution for kernel 2.6
//
// Revision 1.63  2004/04/12 09:48:37  klaus
// Release_20040411_x, improved handling of RTR writes
//
// Revision 1.62  2004/04/11 22:03:29  klaus
// cosmetic changes
//
// Revision 1.61  2004/04/10 08:57:26  klaus
// merge finished between HEAD and kernel-2.6 branch
//
// Revision 1.57.2.2  2004/03/21 19:14:57  klaus
// first beta release for kernel 2.6, no support for USB, code for kernel 2.6 only, release_20040320ba
//
// Revision 1.57.2.1  2004/03/21 12:09:09  klaus
// first commit for branch to kernel 2.6 code
//
// Revision 1.60  2004/03/27 16:57:06  klaus
// modified for use with kernels <= 2.2.14
//
// Revision 1.59  2004/03/27 15:10:54  klaus
// prepared for use with gcc 3.x, modified for use with kernels < 2.2.4
//
// Revision 1.58  2004/03/19 22:25:27  klaus
// removed wrong call to dev->cleanup when no PCI irq was assigned, contributed by Garth Zeglin
//
// Revision 1.57  2004/03/04 18:51:23  klaus
// RTR handling and MIPS (partially) support, Release 20040304_x
//
// Revision 1.56  2003/11/19 22:54:39  klaus
// made Release_20031119
//
// Revision 1.55  2003/08/07 20:06:19  klaus
// removed some warnings during conditional compilation
//
// Revision 1.54  2003/06/22 15:34:50  klaus
// added parts to support devfs provided by Philipp Baer (partially untested)
//
// Revision 1.53  2003/06/04 19:26:15  klaus
// adapted to kernel 2.5.69 using GCC 3.2.3 (marcel), released release_20030604_x
//
// Revision 1.50  2003/03/02 12:18:33  klaus
// Release_20030302_?
//
// Revision 1.49  2003/03/02 12:18:33  klaus
// Release_20030302_?
//
// Revision 1.48  2003/03/02 10:45:52  klaus
// resolved conflict while merging USB thread
//
// Revision 1.46.2.26  2003/02/25 20:22:42  klaus
// Release_20030225_?
//
// Revision 1.47  2003/01/17 20:38:13  klaus
// wrong re-update of pcan_main.c, try to repair
//
// Revision 1.46  2002/12/01 17:51:09  klaus
// minor improvement of code
//
// Revision 1.46.2.25  2003/02/16 19:55:52  klaus
// USB Integration, first non public release
//
// Revision 1.46.2.24  2003/02/16 16:36:16  klaus
// pcan_usb_kernel.c returned to main modules
//
// Revision 1.46.2.23  2003/02/09 10:29:20  klaus
// code cleanup, Release_20030208_x
//
// Revision 1.46.2.22  2003/02/08 17:32:43  klaus
// modified to use pcan_usb_kernel as prorietary module
//
// Revision 1.46.2.21  2003/01/29 20:34:20  klaus
// release_20030129_a and release_20030129_u released
//
// Revision 1.46.2.20  2003/01/29 20:34:20  klaus
// release_20030129_a and release_20030129_u released
//
// Revision 1.46.2.19  2003/01/28 23:28:26  klaus
// reorderd pcan_usb.c and pcan_usb_kernel.c, tidied up
//
// Revision 1.46.2.18  2003/01/26 22:35:39  klaus
// it's not allowed to invoke 2 waits for bulk transfer at the same pipe at the same time
//
//****************************************************************************

//****************************************************************************
// INCLUDES
#include <src/pcan_common.h>     // must always be the 1st include
#include <linux/autoconf.h>

//#define KBUILD_MODNAME pcan

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,18)
#include <linux/module.h>
#endif

#include <linux/kernel.h>   // DPRINTK()
#include <linux/slab.h>     // kmalloc()
#include <linux/fs.h>       // everything...
#include <linux/errno.h>    // error codes
#include <linux/types.h>    // size_t
#include <linux/proc_fs.h>  // proc
#include <linux/fcntl.h>    // O_ACCMODE
#include <linux/pci.h>      // all about pci
#include <linux/capability.h> // all about restrictions
#include <linux/param.h>    // because of HZ
#include <asm/system.h>     // cli(), *_flags
#include <asm/uaccess.h>    // copy_...
#include <asm/timex.h>      // get_mtime()

#include <pcan.h>
#include <src/pcan_main.h>
#include <src/pcan_pci.h>
#include <src/pcan_isa.h>
#include <src/pcan_dongle.h>
#ifdef USB_SUPPORT
#include <src/pcan_usb.h>
#endif
#include <src/pcan_fops.h>
#include <src/pcan_fifo.h>
#include <src/pcan_sja1000.h>

//****************************************************************************
// DEFINES
#define DEFAULT_BTR0BTR1    CAN_BAUD_500K  // defaults to 500 kbit/sec
#define DEFAULT_EXTENDED    1              // catch all frames
#define DEFAULT_LISTENONLY  0

//****************************************************************************
// GLOBALS

// filled by module initialisation
char *type[8] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
u16  io[8]    = {0, 0, 0, 0, 0, 0, 0, 0};
u8   irq[8]   = {0, 0, 0, 0, 0, 0, 0, 0};

//----------------------------------------------------------------------------
// the global driver object, create it
struct driverobj pcan_drv = {};

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
//----------------------------------------------------------------------------
// some stuff to support SysFS coming with kernel 2.6
#include <linux/device.h>

// not yet implemented

#endif

//****************************************************************************
// LOCALS

//****************************************************************************
// CODE

//****************************************************************************
// debug utility
void buffer_dump(u8 *pucBuffer, u16 wLineCount)
{
  #ifdef DEBUG
  int i, j;

  for (i = 0; i < wLineCount; i++)
  {
    printk(KERN_DEBUG "%s: %04x ", DEVICE_NAME, i * 16);

    for (j = 0; j < 8; j++)
      printk(" %02x", *pucBuffer++);

    printk(" ");

    for (j = 0; j < 8; j++)
      printk(" %02x", *pucBuffer++);

    printk("\n");
  }
  #endif
}

//----------------------------------------------------------------------------
// request time in msec, may be it becomes more sophisticated in future
u32 get_mtime(void)
{
	#ifdef KERNEL_26
	return (u32)(monotonic_clock() / 1000);
	#else
  return jiffies * (1000 / HZ);
	#endif
}

//----------------------------------------------------------------------------
// is called when 'cat /proc/pcan' invoked
static int pcan_read_procmem(char *page, char **start, off_t offset, int count, int *eof, void *data)
{
  struct pcandev *dev;
  struct list_head *ptr;
  int    len = 0;
  
  DPRINTK(KERN_DEBUG "%s: pcan_read_procmem()\n", DEVICE_NAME);

  len += sprintf(page + len, "\n");
  len += sprintf(page + len, "*--------- PEAK-Systems CAN interfaces (www.peak-system.com) -----------\n");
  len += sprintf(page + len, "*-----------------------  %s  --------------------------\n", pcan_drv.szVersionString);
  len += sprintf(page + len, "*------------------ %d interfaces @ major %03d found ---------------------\n", 
                     pcan_drv.wDeviceCount, pcan_drv.nMajor);
  len += sprintf(page + len, "*n typ ---base--- irq ---read--- ---write-- ---irqs--- ---error-- status\n");
  
  // loop trough my devices
  for (ptr = pcan_drv.devices.next; ptr != &pcan_drv.devices; ptr = ptr->next)
  {
    u32 dwPort = 0;
    u16 wIrq   = 0;
    
    dev = (struct pcandev *)ptr;  
    switch (dev->wType)
    {
      case HW_ISA_SJA:
        dwPort = dev->port.isa.dwPort;
        wIrq   = dev->port.isa.wIrq;
        break;
      case HW_DONGLE_SJA:
      case HW_DONGLE_SJA_EPP:
        dwPort = dev->port.dng.dwPort;
        wIrq   = dev->port.dng.wIrq;
        break;
      case HW_PCI:
        dwPort = dev->port.pci.dwPort;
        wIrq   = dev->port.pci.wIrq;
        break;
      case HW_USB:
        #ifdef USB_SUPPORT
        // get serial number of device
        if (!dev->ucPhysicallyInstalled)
        {
          dev->port.usb.dwSerialNumber   = 0x00dead00;  // it is dead
          dev->port.usb.ucHardcodedDevNr = 0;
        }
        else
        {
          if (pcan_usb_getSerialNumber(dev))
            dev->port.usb.dwSerialNumber = 0;
        }

        dwPort = dev->port.usb.dwSerialNumber;
        wIrq   = dev->port.usb.ucHardcodedDevNr;
        #endif
        break;
    }

    len += sprintf(page + len, "%2d %3s 0x%08x %03d 0x%08x 0x%08x 0x%08x 0x%08x 0x%04x\n",
                        dev->nMinor, dev->type, dwPort, wIrq,  
			                           dev->readFifo.dwTotal, dev->writeFifo.dwTotal, dev->dwInterruptCounter, 
						                           dev->dwErrorCounter, dev->wCANStatus);
  }
  
  len += sprintf(page + len, "\n");
  
  *eof = 1;
  return len;
}

//----------------------------------------------------------------------------
// is called when the device is removed 'rmmod pcan'
void cleanup_module(void)
{
  struct pcandev *dev;
  
  DPRINTK(KERN_DEBUG "%s: cleanup_module()\n", DEVICE_NAME);

  switch (pcan_drv.wInitStep)
  {
    case 3: remove_proc_entry(DEVICE_NAME, NULL);
    case 2:
            #ifndef DEVFS_SUPPORT
            unregister_chrdev(pcan_drv.nMajor, DEVICE_NAME);
            #else
						{
  						struct list_head *ptr;
							
							// unregister all registered devices
							for (ptr = pcan_drv.devices.next; ptr != &pcan_drv.devices; ptr = ptr->next)
								pcan_devfs_unregister((struct pcandev *)ptr);

							// unregister driver
							devfs_unregister_chrdev(pcan_drv.nMajor, DEVICE_NAME);

							// unregister device directory
							if (pcan_drv.devfs_candir)
								devfs_unregister(pcan_drv.devfs_candir);
						}
            #endif
    case 1: 
    case 0: 
            #ifdef USB_SUPPORT
            pcan_usb_deinit();
            #endif

            while (!list_empty(&pcan_drv.devices)) // cycle through the list of devices and remove them
            {
              dev = (struct pcandev *)pcan_drv.devices.prev; // empty in reverse order

              dev->cleanup(dev);

              list_del(&dev->list);
	            
		          // free all device allocted memory 
              kfree(dev);
            }
        
	          pcan_drv.wInitStep = 0;
	}
	
  printk(KERN_INFO "%s: removed.\n", DEVICE_NAME);
}

//----------------------------------------------------------------------------
// init some equal parts of dev
void pcan_soft_init(struct pcandev *dev, char *szType, u16 wType)
{
  #ifdef DEVFS_SUPPORT
  dev->devfs_candev     = NULL;
  #endif
  
  dev->wType            = wType;
  dev->type             = szType; 
	      
  dev->nOpenPaths       = 0;
  dev->nLastError       = 0;
  dev->dwErrorCounter   = 0;
  dev->dwInterruptCounter = 0;
  dev->wCANStatus       = 0;
  dev->pdwInitTime      = &pcan_drv.dwInitTime;
  dev->bExtended        = 1;   // accept all frames
  dev->wBTR0BTR1        = DEFAULT_BTR0BTR1;
  dev->ucCANMsgType     = DEFAULT_EXTENDED;
  dev->ucListenOnly     = DEFAULT_LISTENONLY;

  // set default access functions - only USB overrides
  dev->device_open      = sja1000_open;
  dev->device_release   = sja1000_release;
  dev->device_write     = sja1000_write;

  dev->ucPhysicallyInstalled = 0;  // assume the device is not installed

  atomic_set(&dev->DataSendReady, 1);

  // init fifos
  pcan_fifo_init(&dev->readFifo,   &dev->rMsg[0], &dev->rMsg[READ_MESSAGE_COUNT - 1],  READ_MESSAGE_COUNT,  sizeof(TPCANRdMsg));
  pcan_fifo_init(&dev->writeFifo,  &dev->wMsg[0], &dev->wMsg[WRITE_MESSAGE_COUNT - 1], WRITE_MESSAGE_COUNT, sizeof(TPCANMsg) );
}

//----------------------------------------------------------------------------
// search all pci based devices from peak
static int search_and_create_pci_devices(void)
{
  int result = 0;
  struct pcandev *dev = NULL;
  
  // search pci devices
  DPRINTK(KERN_DEBUG "%s: search_and_create_pci_devices()\n", DEVICE_NAME);
	#ifdef LINUX_26
	if (CONFIG_PCI)
	#else
  if (pci_present())
	#endif
  {
    struct pci_dev *pciDev;
    
    struct pci_dev *from = NULL;
    do
    {
      pciDev = pci_find_device((unsigned int)PCAN_PCI_VENDOR_ID, (unsigned int)PCAN_PCI_DEVICE_ID, from);
      
      if (pciDev != NULL)
      {
        u16 wSubSysID;

        // a PCI device with PCAN_PCI_VENDOR_ID and PCAN_PCI_DEVICE_ID was found
	      from = pciDev;
	        
	      // get the PCI Subsystem-ID
	      result = pci_read_config_word(pciDev, 0x2E, &wSubSysID);
	      if (result)
	        goto fail;

		    // configure the PCI chip, part 1
		    result = pci_write_config_word(pciDev, 0x04, 2);
		    if (result)
		      goto fail;
		      
		    result = pci_write_config_word(pciDev, 0x44, 0);
		    if (result)
		      goto fail;
		    wmb();
		      
	      // make the first device on board
        if ((dev = (struct pcandev *)kmalloc(sizeof(struct pcandev), GFP_KERNEL)) == NULL)
          goto fail;
	  	      
	      pcan_soft_init(dev, "pci", HW_PCI);
	      
			  #ifdef LINUX_22
	      result = pcan_pci_init(dev, (u32)pciDev->base_address[0], (u32)pciDev->base_address[1], 
	                                          (u16)pciDev->irq, (wSubSysID > 3) ? CHANNEL_MASTER : CHANNEL_SINGLE, NULL);
			  #else
	      result = pcan_pci_init(dev, (u32)pciDev->resource[0].start, (u32)pciDev->resource[1].start, 
	                                          (u16)pciDev->irq, (wSubSysID > 3) ? CHANNEL_MASTER : CHANNEL_SINGLE, NULL);				
				#endif
				
			  
		    if (!result)
		      result = sja1000_probe(dev);         
        if (result)
        {
	        dev->cleanup(dev);
          kfree(dev);
	        goto fail;
	      }
	      else
	      {
          dev->ucPhysicallyInstalled = 1;
          list_add_tail(&dev->list, &pcan_drv.devices);  // add this device to the list
	        pcan_drv.wDeviceCount++;
	      }

        if (wSubSysID > 3)
	      {
	        struct pcandev *master_dev = dev;
		
	        // make the second device on board
          if ((dev = (struct pcandev *)kmalloc(sizeof(struct pcandev), GFP_KERNEL)) == NULL)
            goto fail;

	        pcan_soft_init(dev, "pci", HW_PCI);
	      
					#ifdef LINUX_22
	        result = pcan_pci_init(dev, (u32)pciDev->base_address[0], (u32)pciDev->base_address[1] + 0x400, 
		                                          (u16)pciDev->irq, CHANNEL_SLAVE, master_dev);
					#else
	        result = pcan_pci_init(dev, (u32)pciDev->resource[0].start, (u32)pciDev->resource[1].start + 0x400, 
		                                          (u16)pciDev->irq, CHANNEL_SLAVE, master_dev);
					#endif
					
		      if (!result)
		        result = sja1000_probe(dev);            
          if (result)
          {
	          dev->cleanup(dev);
            kfree(dev);
	          goto fail;
	        }
	        else
	        {
            dev->ucPhysicallyInstalled = 1;
            list_add_tail(&dev->list, &pcan_drv.devices);  // add this device to the list
	          pcan_drv.wDeviceCount++;
	        }	
        }
      }
    } while (pciDev != NULL);
  }  
  
  DPRINTK(KERN_DEBUG "%s: search_and_create_pci_devices() is OK\n", DEVICE_NAME);
  
  fail:
  return result;  
}

//----------------------------------------------------------------------------
// create all declared isa devices
static int create_isa_devices(void)
{
  int result = 0;
  int i;
  struct pcandev *dev = NULL;

  // create isa devices
  DPRINTK(KERN_DEBUG "%s: create_isa_devices()\n", DEVICE_NAME);
  for (i = 0; ((i < 8) && (type[i] != NULL)); i++)
  {
    if (!strncmp(type[i], "isa", 4))
    {
      if ((dev = (struct pcandev *)kmalloc(sizeof(struct pcandev), GFP_KERNEL)) == NULL)
        goto fail;

		  pcan_soft_init(dev, type[i], HW_ISA_SJA);

      result = pcan_isa_init(dev, (u32)io[i], (u16)irq[i]);
		  if (!result)
		    result = sja1000_probe(dev);
      if (result)
      {
	      dev->cleanup(dev);
        kfree(dev);
	      goto fail;
	    }
	    else
	    {
        dev->ucPhysicallyInstalled = 1;
        list_add_tail(&dev->list, &pcan_drv.devices);  // add this device to the list
	      pcan_drv.wDeviceCount++;
	    }	      
    }
  }
  
  DPRINTK(KERN_DEBUG "%s: create_isa_devices() is OK\n", DEVICE_NAME);
  
  fail:
  return result;
}

//----------------------------------------------------------------------------
// create all declared dongle devices
static int create_dongle_devices(void)
{
  int result = 0;
  int i;
  struct pcandev *dev = NULL;

  DPRINTK(KERN_DEBUG "%s: create_dongle_devices()\n", DEVICE_NAME);
  for (i = 0; ((i < 8) && (type[i] != NULL)); i++)
  {
    if (!strncmp(type[i], "sp", 4) || !strncmp(type[i], "epp", 4))
    {
      if ((dev = (struct pcandev *)kmalloc(sizeof(struct pcandev), GFP_KERNEL)) == NULL)
        goto fail;
	
		  pcan_soft_init(dev, type[i], (!strncmp(type[i], "sp", 4)) ? HW_DONGLE_SJA : HW_DONGLE_SJA_EPP);

      result = pcan_dongle_init(dev, (u32)io[i], (u16)irq[i], type[i]);
      if (result)
      {
	      dev->cleanup(dev);
          kfree(dev);
	      goto fail;
	    }
	    else
	    {
        list_add_tail(&dev->list, &pcan_drv.devices);  // add this device to the list	      
	      pcan_drv.wDeviceCount++;
	    }	      
    }
  }
 
  DPRINTK(KERN_DEBUG "%s: create_dongle_devices() is OK\n", DEVICE_NAME);

  fail:
  return result; 
}

#ifdef USB_SUPPORT
//----------------------------------------------------------------------------
// init for usb based devices from peak
static int register_usb_devices(void)
{
  int err;

  DPRINTK(KERN_DEBUG "%s: register_usb_devices()\n", DEVICE_NAME);

  if (!(err = pcan_usb_init()))
  {
    DPRINTK(KERN_DEBUG "%s: register_usb_devices() is OK\n", DEVICE_NAME);
  }

  return err;
}
#endif

#ifdef LINUX_22
//----------------------------------------------------------------------------
// replacement for kernel 2.4.x function
// original from sysdep.h  from the book
// "Linux Device Drivers" by Alessandro Rubini and Jonathan Corbet, published by O'Reilly & Associates.
static struct proc_dir_entry *create_proc_read_entry(const char *name, mode_t mode, 
                                        struct proc_dir_entry *base, read_proc_t *read_proc, void * data)
{
  struct proc_dir_entry *res = create_proc_entry(name, mode, base);
  if (res) 
  {
    res->read_proc=read_proc;
    res->data=data;
  }
  return res;
}
#endif

//----------------------------------------------------------------------------
// is called when the device is installed 'insmod pcan.o'
int init_module(void)
{
  int result = 0;

  pcan_drv.wInitStep       = 0;
  pcan_drv.dwInitTime      = get_mtime();     // store time for timestamp relation, increments in msec
  pcan_drv.szVersionString = CURRENT_RELEASE; // get the release name global
  pcan_drv.nMajor          = PCAN_MAJOR;
  
  printk(KERN_INFO "%s: %s\n", DEVICE_NAME, pcan_drv.szVersionString);

  INIT_LIST_HEAD(&pcan_drv.devices);
  pcan_drv.wDeviceCount = 0;

  // create device file system directory
  #ifdef DEVFS_SUPPORT
  pcan_drv.devfs_candir = devfs_mk_dir(NULL, PCAN_DEVFS_DIR, NULL);
  if (!pcan_drv.devfs_candir)
    return -EBUSY;
  #endif

  // search pci devices
  if ((result = search_and_create_pci_devices()))
    goto fail;

  // create isa devices
  if ((result = create_isa_devices()))
    goto fail;

  // create dongle devices
  if ((result = create_dongle_devices()))
    goto fail;

  #ifdef USB_SUPPORT
  // register usb devices only
  if ((result = register_usb_devices()))
    goto fail;
  #else
  // no device found, stop all
  if (!pcan_drv.wDeviceCount)
    goto fail;
  #endif

  pcan_drv.wInitStep = 1;

  // register the driver by the OS
  #ifndef DEVFS_SUPPORT
  if ((result = register_chrdev(pcan_drv.nMajor, DEVICE_NAME, &pcan_fops)) < 0)
    goto fail;
  #else
  if ((result = devfs_register_chrdev(pcan_drv.nMajor, DEVICE_NAME, &pcan_fops)) < 0)
    goto fail;
  #endif

  if (!pcan_drv.nMajor)
    pcan_drv.nMajor = result;

  #ifdef DEVFS_SUPPORT
	{
		struct list_head *ptr;
		
		// create all device entries except those for USB
		for (ptr = pcan_drv.devices.next; ptr != &pcan_drv.devices; ptr = ptr->next)
			pcan_devfs_register((struct pcandev *)ptr);
	}
  #endif
  
  pcan_drv.wInitStep = 2;

  // create the proc entry
  if (create_proc_read_entry(DEVICE_NAME, 0, NULL, pcan_read_procmem, NULL) == NULL)
  {
    result = -ENODEV; // maybe wrong if there is no proc filesystem configured
    goto fail;
  }

  pcan_drv.wInitStep = 3;

  printk(KERN_INFO "%s: major %d.\n", DEVICE_NAME, pcan_drv.nMajor);
  return 0; // succeed

  fail:
  cleanup_module();
  return result;
}

//----------------------------------------------------------------------------
// support for device files system introduced with kernels greater 2.3.46
#ifdef DEVFS_SUPPORT
int pcan_devfs_register(struct pcandev *dev)
{
  char deviceNumber[6];
  char deviceName[32]; 
  char symlink[32];
  
  DPRINTK(KERN_DEBUG "%s: pcan_devfs_register()\n", DEVICE_NAME);

  snprintf(deviceNumber, 5, "%d", dev->nMinor);
  snprintf(deviceName, 31, "%s/%s", PCAN_DEVFS_DIR, deviceNumber); // made out of path and deviceName
  snprintf(symlink, 31, "%s%d", DEVICE_NAME, dev->nMinor);

  if (pcan_drv.devfs_candir)
  {
    dev->devfs_candev = devfs_register(pcan_drv.devfs_candir, deviceNumber, DEVFS_FL_DEFAULT,
                                       pcan_drv.nMajor, dev->nMinor, 
                                       S_IFCHR | S_IRUGO | S_IWUSR | S_IWGRP | S_IWOTH,
                                       &pcan_fops, dev);

    DPRINTK(KERN_DEBUG "%s: devfs_register(0x%08x, %s) = 0x%08x\n", DEVICE_NAME, (u32)pcan_drv.devfs_candir, deviceNumber, (u32)dev->devfs_candev);

    // create a symbolic link to support old style device names
    if (dev->devfs_candev)
    {
      devfs_mk_symlink(NULL, symlink, DEVFS_FL_DEFAULT, deviceName, NULL, NULL);
      DPRINTK(KERN_DEBUG "%s: devfs_mk_symlink(%s, %s)\n", DEVICE_NAME, symlink, deviceName);
    }
  }

  return 0;
}

void pcan_devfs_unregister(struct pcandev *dev)
{
  DPRINTK(KERN_DEBUG "%s: pcan_devfs_unregister(0x%08x)\n", DEVICE_NAME, (u32)dev->devfs_candev);

  if (dev->devfs_candev)
  {
    devfs_unregister(dev->devfs_candev);
    dev->devfs_candev = NULL;
  }
}
#endif



// end

